{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ea75a444",
   "metadata": {},
   "source": [
    "# LangGraph ChatBot 开发指南\n",
    "\n",
    "LangGraph 是一个灵活的 Agent 开发框架，可以帮助您设计复杂的对话工作流和智能代理。\n",
    "\n",
    "本指南将指导您如何使用 LangGraph 构建一个支持**多轮对话的智能客服（聊天机器人）**。\n",
    "\n",
    "我们将从一个基础的聊天机器人开始，逐步添加更多高级功能，并介绍关键的 LangGraph 概念。\n",
    "\n",
    "通过以下几个部分的学习，您将掌握如何逐步构建、增强和管理该聊天机器人。\n",
    "\n",
    "1. **构建Chatbot**： 基于 GPT-4o-mini 构建基础聊天机器人\n",
    "2. **联网查询工具**：为聊天机器人添加工具（结合网络搜索回答问题）\n",
    "3. **增加记忆系统**：在多次调用中保持对话状态\n",
    "4. **人工介入对话**：将复杂查询路由给人工审查或手动更新内容\n",
    "5. **查询历史对话**：通过状态图状态和配置查看/打印历史对话\n",
    "----------\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "771ff467-f475-4461-b1c1-3678fbf64298",
   "metadata": {},
   "source": [
    "## LangGraph 核心对象介绍\n",
    "\n",
    "#### **StateGraph 对象**\n",
    "\n",
    "`StateGraph` 是 LangGraph 中的核心概念之一，它定义了聊天机器人或其他任务处理流程的结构。它是一种状态机，通过节点（nodes）和边（edges）来表示系统的状态变化。以下是 `StateGraph` 的关键点：\n",
    "\n",
    "- **定义流程图**：`StateGraph` 是用来创建流程图的对象，图中的每个节点代表一个任务或计算步骤（如调用 LLM、工具或函数），每个边（edge）定义了从一个节点到下一个节点的流向。\n",
    "  \n",
    "- **状态管理**：`StateGraph` 通过状态（`State`）来管理流程中的数据和上下文。每个节点都会接收当前的状态，并且返回一个更新后的状态。这种机制保证了在多轮对话或任务中，机器人能够持续维护上下文信息。\n",
    "\n",
    "- **消息更新**：在 `StateGraph` 中，我们可以定义如何更新状态，例如使用 `add_messages` 函数，表示将消息追加到已有的消息列表，而不是覆盖旧消息。\n",
    "\n",
    "#### StateGraph 的使用步骤\n",
    "1. **定义 State**：首先需要定义状态（`State`），例如用字典来存储消息、工具调用的结果等内容。\n",
    "2. **添加节点**：每个节点表示一个任务单元，可以是任意的 Python 函数。\n",
    "3. **添加边**：通过 `add_edge()` 方法指定节点之间的流向，例如从聊天节点到工具节点，再到结束节点。\n",
    "4. **编译图**：通过 `compile()` 方法将流程图编译为可执行的 `CompiledGraph`。\n",
    "\n",
    "-----------\n",
    "\n",
    "#### **CompiledGraph 对象**\n",
    "\n",
    "`CompiledGraph` 是由 `StateGraph` 编译得到的实际可执行对象。它负责执行在 `StateGraph` 中定义的流程，并处理每个节点的任务。`CompiledGraph` 通过状态的流动来管理整个对话或任务的执行。以下是 `CompiledGraph` 的关键点：\n",
    "\n",
    "- **流程执行**：`CompiledGraph` 是 `StateGraph` 的实际运行版本。当你调用 `stream()` 或 `invoke()` 方法时，它会依次执行图中的节点，并根据每个节点的输出状态决定下一个节点的执行。\n",
    "\n",
    "- **状态检查点（Checkpointing）**：通过使用检查点机制，`CompiledGraph` 可以在每个节点执行完后保存当前的状态，允许任务暂停并在之后恢复。例如，您可以为机器人添加记忆功能或支持“时间旅行”（回到之前的某个状态点）。\n",
    "\n",
    "- **动态路由**：`CompiledGraph` 还支持动态路由（Conditional Edges），允许根据当前状态动态决定下一步执行的节点。这使得机器人可以根据上下文或工具的输出灵活地调整行为。\n",
    "\n",
    "------------\n",
    "\n",
    "#### 关系总结\n",
    "\n",
    "- `StateGraph` 用于定义和构建一个聊天机器人或任务处理流程的结构，通过节点和边来管理流程和状态的流动。\n",
    "- `CompiledGraph` 是 `StateGraph` 编译后的版本，负责实际的流程执行、状态管理和检查点保存。\n",
    "\n",
    "它们结合在一起，提供了一个灵活、可扩展的方式来构建复杂的多步骤对话机器人或任务执行系统。\n",
    "\n",
    "------------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfa6d74b-5d3c-44c3-b6b6-794f82f366b8",
   "metadata": {},
   "source": [
    "## LangGraph 核心方法介绍\n",
    "\n",
    "`graph.stream` 是 LangGraph 中的一个核心方法，用于执行编译后的状态图（`CompiledGraph`）并以流式（streaming）的方式处理每个节点。通过 `stream` 方法，系统可以逐步执行对话或任务的每个步骤，并在每一步中返回中间结果。这种方式特别适用于长时间任务、逐步处理的工具调用或连续的对话。\n",
    "\n",
    "### `graph.stream` 的核心功能\n",
    "\n",
    "1. **流式执行**：\n",
    "   - `graph.stream` 允许您在每个节点执行时获得结果，类似于生成器的工作方式。在对话或任务流程中，机器人每经过一个节点（如调用工具、获取外部数据、与用户对话），都会返回该节点的执行结果。这使得系统可以逐步处理复杂的任务或多轮对话，而不是一次性等待所有步骤完成。\n",
    "\n",
    "2. **中间状态反馈**：\n",
    "   - 使用 `stream` 方法时，开发者可以在每一步获得当前的中间状态（如对话消息、工具调用的结果）。这对于调试、错误处理和用户实时反馈非常有帮助。\n",
    "   - 比如，在对话中，系统可以在用户输入每一条消息后逐步处理，逐步生成回答，而不是一次性返回最终结果。\n",
    "\n",
    "3. **支持多轮对话**：\n",
    "   - 通过 `stream`，可以让机器人保持对话的状态，使其能够处理复杂的多轮对话。系统在每一步都会保存对话的上下文，并在接收到新消息时恢复对话的状态，继续处理。\n",
    "\n",
    "4. **支持工具调用和多节点执行**：\n",
    "   - `stream` 方法不仅支持对话，还可以用于工具调用等任务。在每个工具节点执行时，系统会返回工具的执行结果，允许您对每个步骤的输出进行检查或处理。\n",
    "   - 它特别适合那些需要多个步骤或节点共同执行的任务，保证每个节点依次运行。\n",
    "\n",
    "---\n",
    "\n",
    "### 典型用法（涵盖1-3部分的重要功能）\n",
    "\n",
    "#### 1. 执行对话\n",
    "\n",
    "```python\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "user_input = \"Hi, can you tell me today's weather?\"\n",
    "\n",
    "# 使用 stream 方法来执行对话\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},  # 传入用户的输入消息\n",
    "    config,  # 对话线程配置，用于标识对话的唯一 ID\n",
    "    stream_mode=\"values\"  # 以流式返回数据\n",
    ")\n",
    "\n",
    "# 遍历每个事件，并输出机器人生成的消息\n",
    "for event in events:\n",
    "    print(event[\"messages\"][-1].content)  # 打印最后一条消息的内容\n",
    "```\n",
    "\n",
    "在这个示例中，`graph.stream` 逐步处理用户的输入，并返回每个步骤的结果。由于使用了 `stream_mode=\"values\"`，系统以流式返回对话的中间结果。\n",
    "\n",
    "#### 2. 执行工具调用\n",
    "\n",
    "```python\n",
    "user_input = \"Please search for the latest news about AI.\"\n",
    "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
    "\n",
    "# 使用 stream 方法来执行带有工具调用的任务\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},  # 用户输入的消息\n",
    "    config,\n",
    "    stream_mode=\"values\"\n",
    ")\n",
    "\n",
    "# 遍历事件，获取工具的调用结果\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        # 打印工具的调用结果或机器人回复\n",
    "        print(event[\"messages\"][-1].content)\n",
    "```\n",
    "\n",
    "这个示例展示了如何通过 `graph.stream` 执行包含工具调用的任务。每个工具的调用结果都会在事件中返回，并且可以即时处理和反馈。\n",
    "\n",
    "---\n",
    "\n",
    "### `graph.stream` 的参数\n",
    "\n",
    "- **`inputs`**：输入的字典数据，通常包含对话的消息（如 `{\"messages\": [(\"user\", user_input)]}`），表示用户输入了什么信息。`inputs` 是机器人任务的初始数据。\n",
    "- **`config`**：配置参数，用于指定对话线程 ID、流执行模式等选项。它允许为每个对话或任务指定唯一的标识符，以便系统可以跟踪对话的上下文或任务状态。\n",
    "- **`stream_mode`**：指定返回数据的模式。常见的取值是 `\"values\"`，表示返回处理结果的流式数据。其他模式也可以根据具体需求进行调整。\n",
    "\n",
    "---\n",
    "\n",
    "### 适用场景\n",
    "\n",
    "1. **多轮对话机器人**：在复杂的对话场景中，用户可能与机器人进行多轮对话，并且希望机器人能够持续记住对话上下文。`graph.stream` 允许在每一轮对话中返回中间结果，并保持对话的流畅性和连贯性。\n",
    "  \n",
    "2. **逐步执行复杂任务**：例如，机器人在执行需要多个步骤的任务时，可以通过 `stream` 方法在每个步骤执行完后返回结果，而不是一次性执行整个任务。这种逐步处理的机制可以确保任务的每一步都被正确执行，并且可以随时处理异常情况。\n",
    "\n",
    "3. **实时反馈**：在实时应用中，用户希望能够尽快获得机器人的反馈。通过 `graph.stream`，系统可以在每个步骤中即时返回处理结果，让用户感受到流畅的交互体验。\n",
    "\n",
    "---\n",
    "\n",
    "### 总结\n",
    "\n",
    "`graph.stream` 是一个强大的工具，用于以流式处理对话和任务的每个步骤。它特别适合那些需要逐步执行和处理的任务场景，如多轮对话、复杂的工具调用或任务处理。通过 `stream`，系统可以在每一步中即时返回结果，并保持任务或对话的上下文信息。\n",
    "\n",
    "-----"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77f3f01c-21f0-47be-a32b-3895f46f1b65",
   "metadata": {},
   "source": [
    "## 第 1 部分：构建基础聊天机器人\n",
    "\n",
    "我们将首先使用 LangGraph 创建一个简单的聊天机器人。该机器人将直接响应用户消息。虽然简单，但它将展示 LangGraph 构建的核心概念。在本部分结束时，您将构建一个基础的聊天机器人。\n",
    "\n",
    "### 1. 安装依赖包\n",
    "\n",
    "首先，安装所需的软件包："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "bcd46847",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "# 安装 LangGraph 和 LangSmith，用于状态图和跟踪\n",
    "%pip install -U langgraph langsmith"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9cb2298",
   "metadata": {},
   "source": [
    "### 2. 设置 LangSmith API 密钥"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d9db1b1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# 开启 LangSmith 跟踪，便于调试和查看详细执行信息\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_PROJECT\"] = \"ChatBot\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eeb060c3",
   "metadata": {},
   "source": [
    "### 3. 定义聊天机器人的状态图 `StateGraph`\n",
    "\n",
    "我们将添加节点以表示聊天机器人的逻辑和功能，并添加边来指定如何在功能之间进行转换。\n",
    "\n",
    "#### 代码解析\n",
    "\n",
    "当定义一个状态图时，首先要定义图的状态 `State`。`State` 包含图的状态结构以及 reducer 函数，它们指定如何应用状态更新。\n",
    "\n",
    "在本例中，`State` 是一个带有单一键 `messages` 的 `TypedDict`，该键使用 `add_messages` 函数作为注解，告诉 LangGraph 应该将新消息追加到现有消息列表中，而不是覆盖它。\n",
    "\n",
    "没有注解的状态键将被覆盖，存储最新的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a959d214",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "# 定义状态类型，继承自 TypedDict，并使用 add_messages 函数将消息追加到现有列表\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "# 创建一个状态图对象，传入状态定义\n",
    "graph_builder = StateGraph(State)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4950d16",
   "metadata": {},
   "source": [
    "现在，状态图知道了两件事：\n",
    "\n",
    "1. 每个我们定义的节点都会接收当前的 `State` 作为输入，并返回一个更新该状态的值。\n",
    "2. `messages` 将追加到当前列表中，而不是直接覆盖。这通过 `Annotated` 语法中的预构建 `add_messages` 函数传达。\n",
    "\n",
    "### 4. 添加聊天节点\n",
    "\n",
    "节点表示一个计算单元。它们通常是常规的 Python 函数。\n",
    "\n",
    "**代码解析：**\n",
    "\n",
    "聊天机器人节点函数（`chatbot`）接收当前的 `State` 作为输入，并返回一个包含更新后的 `messages` 列表的字典。这是所有 LangGraph 节点函数的基础模式。\n",
    "\n",
    "在 `State` 中的 `add_messages` 函数会将 LLM 的响应消息追加到现有的消息列表中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "1f1607f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "# 初始化一个 GPT-4o-mini 模型\n",
    "chat_model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "\n",
    "# 定义聊天机器人的节点函数，接收当前状态并返回更新的消息列表\n",
    "def chatbot(state: State):\n",
    "    return {\"messages\": [chat_model.invoke(state[\"messages\"])]}\n",
    "\n",
    "# 第一个参数是唯一的节点名称，第二个参数是每次节点被调用时的函数或对象\n",
    "graph_builder.add_node(\"chatbot\", chatbot)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1ba47db",
   "metadata": {},
   "source": [
    "### 5. 定义聊天机器人对话流程（状态图的起终点）\n",
    "\n",
    "- **起点（START）**：每次运行时，从哪里开始工作。\n",
    "- **终点（END）**：每次运行此节点时，程序可以退出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7f06c7de",
   "metadata": {},
   "outputs": [],
   "source": [
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "graph_builder.add_edge(\"chatbot\", END)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "848f8d09",
   "metadata": {},
   "source": [
    "### 6. 编译图并可视化\n",
    "\n",
    "最后，我们需要能够运行我们的状态图。\n",
    "\n",
    "在图构建器上调用 `compile()`，这会创建一个可执行的 `CompiledGraph`对象。\n",
    "\n",
    "我们可以使用该图(`CompiledGraph`)来调用聊天机器人。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9ff06359",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 编译状态图并生成可执行图对象\n",
    "graph = graph_builder.compile()\n",
    "\n",
    "# 你可以使用 get_graph 方法来可视化图，并结合 draw 方法（如 draw_ascii 或 draw_png）\n",
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "except Exception:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e7be101",
   "metadata": {},
   "source": [
    "### 7. 运行图\n",
    "\n",
    "**恭喜！您已经使用 LangGraph 构建了第一个聊天机器人。这个机器人可以通过接受用户输入并生成 LLM 回复来进行基本对话。**\n",
    "\n",
    "现在运行聊天机器人！\n",
    "\n",
    "#### 代码解析\n",
    "\n",
    "1. **聊天循环**：\n",
    "   - `while True:` 启动一个持续的聊天循环，用户可以不断输入问题或命令与聊天机器人互动。\n",
    "\n",
    "2. **获取用户输入**：\n",
    "   - 使用 `input(\"User: \")` 来获取用户的输入消息，并将其赋值给 `user_input` 变量。\n",
    "\n",
    "3. **退出条件**：\n",
    "   - 如果用户输入 `\"quit\"`、`\"exit\"` 或 `\"q\"`，系统会通过 `break` 语句退出聊天循环，结束程序运行。\n",
    "\n",
    "4. **将用户消息传递给聊天机器人**：\n",
    "   - 通过调用 `graph.stream({\"messages\": (\"user\", user_input)})`，将用户的输入传递给聊天机器人模型。`graph.stream` 方法会根据输入的消息生成相应的回复。\n",
    "   - 这里 `\"messages\": (\"user\", user_input)` 表示传递的是用户的输入消息。\n",
    "\n",
    "5. **处理机器人的回复**：\n",
    "   - 遍历 `event.values()` 中的每个值，从机器人生成的回复中提取最后一条消息，并使用 `print(\"Assistant:\", value[\"messages\"][-1].content)` 打印输出聊天机器人的回复内容。\n",
    "\n",
    "该代码是一个简单的聊天机器人框架，用户可以在命令行中输入问题，机器人会根据用户的输入实时生成回复。如果用户输入退出指令，程序会结束对话循环。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "12e6c741",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "User:  详细介绍下 LangGraph 项目\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Assistant: LangGraph 是一个专注于自然语言处理（NLP）和图结构数据的项目，旨在通过结合语言模型和图结构来实现更高级的语义理解与推理。该项目的核心思想是利用图的结构化特性来增强语言模型的能力，使其在处理复杂的语言任务时更具有效性和灵活性。\n",
      "\n",
      "### 项目背景与目标\n",
      "- **背景**：随着人工智能和机器学习的发展，自然语言处理技术得到了广泛应用，但在某些复杂的任务中，传统的语言模型往往面临挑战。图结构数据能够更好地表示实体之间的关系，因此将其与语言模型结合可能会带来更好的效果。\n",
      "- **目标**：LangGraph 的目标是开发一种新的方法论，将图模型与语言模型相结合，以提高在信息抽取、问答系统、知识图谱构建等任务中的性能。\n",
      "\n",
      "### 主要特点\n",
      "1. **图结构与语言结合**：LangGraph 通过构建语言数据的图表示，能够捕捉到文本中隐含的关系和语义信息。\n",
      "2. **增强的推理能力**：通过图结构，模型可以更好地进行多跳推理，处理复杂的语言理解任务。\n",
      "3. **灵活性与扩展性**：LangGraph 的架构设计允许用户根据不同的应用需求进行自定义和扩展，适用于多种 NLP 任务。\n",
      "\n",
      "### 应用场景\n",
      "- **信息抽取**：从文本中提取实体及其关系，并构建知识图谱。\n",
      "- **问答系统**：利用图结构提高问答系统的准确性，尤其是在需要多步推理的情况下。\n",
      "- **文本生成**：基于图的结构生成更为连贯和语义丰富的文本。\n",
      "\n",
      "### 研究与开发\n",
      "LangGraph 项目涉及多个研究领域，包括：\n",
      "- 自然语言处理\n",
      "- 图论与图神经网络\n",
      "- 机器学习与深度学习\n",
      "\n",
      "研究团队可能会探索不同的模型架构、训练方法和评价指标，以验证 LangGraph 的有效性和实用性。\n",
      "\n",
      "### 结论\n",
      "LangGraph 代表了一种新的方向，将语言模型与图结构相结合，旨在推动自然语言处理领域的发展。通过充分利用图的结构信息，LangGraph 有潜力在多个应用领域中实现更高的性能和更好的用户体验。对于研究人员和开发者而言，这一项目提供了丰富的探索空间和实践机会。\n"
     ]
    },
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "User:  q\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Goodbye!\n"
     ]
    }
   ],
   "source": [
    "# 开始一个简单的聊天循环\n",
    "while True:\n",
    "    # 获取用户输入\n",
    "    user_input = input(\"User: \")\n",
    "    \n",
    "    # 可以随时通过输入 \"quit\"、\"exit\" 或 \"q\" 退出聊天循环\n",
    "    if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
    "        print(\"Goodbye!\")  # 打印告别信息\n",
    "        break  # 结束循环，退出聊天\n",
    "\n",
    "    # 将每次用户输入的内容传递给 graph.stream，用于聊天机器人状态处理\n",
    "    # \"messages\": (\"user\", user_input) 表示传递的消息是用户输入的内容\n",
    "    for event in graph.stream({\"messages\": (\"user\", user_input)}):\n",
    "        \n",
    "        # 遍历每个事件的值\n",
    "        for value in event.values():\n",
    "            # 打印输出 chatbot 生成的最新消息\n",
    "            print(\"Assistant:\", value[\"messages\"][-1].content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "497df061",
   "metadata": {},
   "source": [
    "--------------\n",
    "#### 运行结果分析\n",
    "\n",
    "问：详细介绍下 LangGraph 项目\n",
    "\n",
    "gpt-4o-mini 训练数据截止日期，在 LangGraph 项目推出前。\n",
    "\n",
    "因此，直接让模型生成 LangGraph 相关介绍时，会出现事实性的问题。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a64039fb",
   "metadata": {},
   "source": [
    "## 第 2 部分：为聊天机器人添加工具\n",
    "\n",
    "为了处理我们聊天机器人无法“记住”回答的问题，我们将集成一个 Web 搜索工具 [Tavily Search](\n",
    "https://python.langchain.com/v0.2/docs/integrations/tools/tavily_search/)。\n",
    "\n",
    "我们的机器人可以使用这个工具找到相关信息，并提供更好的回复。\n",
    "\n",
    "### 1. 安装依赖并设置 Tavily API\n",
    "\n",
    "访问 [Tavily](https://tavily.com/) 官网，注册账号并生成你的 `TAVILY_API_KEY`。\n",
    "\n",
    "Tavily 提供 1000次/月的免费检索额度。\n",
    "\n",
    "在开始之前，确保您已安装 Tavily 搜索引擎所需的包并在环境变量中设置了 `TAVILY_API_KEY`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9fe20f5f",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "# 安装 Tavily 搜索引擎的 Python 包\n",
    "%pip install -U tavily-python\n",
    "%pip install -U langchain_community"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fb1758a2-7208-4aa4-8c60-4ec2e57b1783",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import getpass\n",
    "# import os\n",
    "\n",
    "# if not os.environ.get(\"TAVILY_API_KEY\"):\n",
    "#     os.environ[\"TAVILY_API_KEY\"] = getpass.getpass(\"Tavily API key:\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4448a461",
   "metadata": {},
   "source": [
    "### 2. 定义工具"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5ec05131",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',\n",
       "  'content': 'Nodes: Nodes are the building blocks of your LangGraph. Each node represents a function or a computation step. You define nodes to perform specific tasks, such as processing input, making ...'},\n",
       " {'url': 'https://medium.com/@kbdhunga/beginners-guide-to-langgraph-understanding-state-nodes-and-edges-part-1-897e6114fa48',\n",
       "  'content': 'Each node in a LangGraph graph has the ability to access, read, and write to the state. When a node modifies the state, it effectively broadcasts this information to all other nodes within the graph .'}]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_community.tools.tavily_search import TavilySearchResults\n",
    "\n",
    "# 定义 Tavily 搜索工具，最大搜索结果数设置为 2\n",
    "tool = TavilySearchResults(max_results=2)\n",
    "tools = [tool]\n",
    "\n",
    "# 测试工具调用\n",
    "tool.invoke(\"What's a 'node' in LangGraph?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07ba26bd",
   "metadata": {},
   "source": [
    "-----------------------------\n",
    "#### 搜索工具结果 如上所示\n",
    "\n",
    "工具返回的是页面摘要，供我们的聊天机器人用于回答问题。\n",
    "\n",
    "### 3. 将工具集成到状态图中\n",
    "\n",
    "以下步骤与第 1 部分类似，只不过我们在 `LLM` 上添加了 `bind_tools`，这使得 LLM 可以在需要时调用搜索工具。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "32631ea0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from langchain_openai import ChatOpenAI\n",
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "# 定义状态\n",
    "class State(TypedDict):\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# 初始化 LLM 并绑定搜索工具\n",
    "chat_model = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "llm_with_tools = chat_model.bind_tools(tools)\n",
    "\n",
    "# 更新聊天机器人节点函数，支持工具调用\n",
    "def chatbot(state: State):\n",
    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
    "\n",
    "# 将更新后的节点添加到状态图中\n",
    "graph_builder.add_node(\"chatbot\", chatbot)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c8c99bb",
   "metadata": {},
   "source": [
    "\n",
    "### 4. 处理工具调用\n",
    "\n",
    "我们需要创建一个函数来运行工具。我们通过向图中添加一个新节点来实现这一点。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7e8e3ba9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "from langchain_core.messages import ToolMessage\n",
    "\n",
    "# 定义 BasicToolNode，用于执行工具请求\n",
    "class BasicToolNode:\n",
    "    \"\"\"一个在最后一条 AIMessage 中执行工具请求的节点。\n",
    "    \n",
    "    该节点会检查最后一条 AI 消息中的工具调用请求，并依次执行这些工具调用。\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, tools: list) -> None:\n",
    "        # tools 是一个包含所有可用工具的列表，我们将其转化为字典，\n",
    "        # 通过工具名称（tool.name）来访问具体的工具\n",
    "        self.tools_by_name = {tool.name: tool for tool in tools}\n",
    "\n",
    "    def __call__(self, inputs: dict):\n",
    "        \"\"\"执行工具调用\n",
    "        \n",
    "        参数:\n",
    "        inputs: 包含 \"messages\" 键的字典，\"messages\" 是对话消息的列表，\n",
    "                其中最后一条消息可能包含工具调用的请求。\n",
    "        \n",
    "        返回:\n",
    "        包含工具调用结果的消息列表\n",
    "        \"\"\"\n",
    "        # 获取消息列表中的最后一条消息，判断是否包含工具调用请求\n",
    "        if messages := inputs.get(\"messages\", []):\n",
    "            message = messages[-1]\n",
    "        else:\n",
    "            raise ValueError(\"输入中未找到消息\")\n",
    "\n",
    "        # 用于保存工具调用的结果\n",
    "        outputs = []\n",
    "\n",
    "        # 遍历工具调用请求，执行工具并将结果返回\n",
    "        for tool_call in message.tool_calls:\n",
    "            # 根据工具名称找到相应的工具，并调用工具的 invoke 方法执行工具\n",
    "            tool_result = self.tools_by_name[tool_call[\"name\"]].invoke(\n",
    "                tool_call[\"args\"]\n",
    "            )\n",
    "            # 将工具调用结果作为 ToolMessage 保存下来\n",
    "            outputs.append(\n",
    "                ToolMessage(\n",
    "                    content=json.dumps(tool_result),  # 工具调用的结果以 JSON 格式保存\n",
    "                    name=tool_call[\"name\"],  # 工具的名称\n",
    "                    tool_call_id=tool_call[\"id\"],  # 工具调用的唯一标识符\n",
    "                )\n",
    "            )\n",
    "        # 返回包含工具调用结果的消息\n",
    "        return {\"messages\": outputs}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "75c139e4-af01-4cc8-987e-16b2ec82a662",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将 BasicToolNode 添加到状态图中\n",
    "tool_node = BasicToolNode(tools=[tool])\n",
    "graph_builder.add_node(\"tools\", tool_node)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f833a146",
   "metadata": {},
   "source": [
    "\n",
    "### 5. 添加条件边\n",
    "\n",
    "条件边将控制流从一个节点路由到另一个节点。条件边通常包含 `if` 语句，以根据当前状态将控制流路由到不同的节点。\n",
    "\n",
    "#### 代码解析\n",
    "\n",
    "我们定义一个路由函数 `route_tools`，检查聊天机器人的输出中是否包含工具调用。此函数会在聊天机器人节点完成后检查，决定下一步走向工具节点还是结束对话。\n",
    "\n",
    "1. **`route_tools` 函数**：这是一个路由函数，用来决定机器人在对话流程中的下一步。它会检查状态中的最后一条消息，判断该消息是否包含工具调用请求。\n",
    "   - 如果有工具调用请求（通过 `tool_calls` 属性判断），返回 `\"tools\"`，表示需要执行工具节点。\n",
    "   - 如果没有工具调用请求，则返回 `\"__end__\"`，表示对话流程结束。\n",
    "\n",
    "2. **`add_conditional_edges`**：这是 LangGraph 中用于条件路由的函数。它允许我们根据 `route_tools` 函数的返回值决定下一个要执行的节点。比如：\n",
    "   - 如果返回值是 `\"tools\"`，则执行工具节点。\n",
    "   - 如果返回值是 `\"__end__\"`，则结束流程。\n",
    "\n",
    "3. **`add_edge`**：当工具节点执行完成后，机器人会返回到 `chatbot` 节点继续对话。\n",
    "\n",
    "4. **编译状态图**：最后，使用 `graph_builder.compile()` 编译状态图，生成一个 `CompiledGraph`，这个对象可以实际执行对话流程。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "fafcaabc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "\n",
    "# 定义路由函数，检查工具调用\n",
    "def route_tools(\n",
    "    state: State,\n",
    ") -> Literal[\"tools\", \"__end__\"]:\n",
    "    \"\"\"\n",
    "    使用条件边来检查最后一条消息中是否有工具调用。\n",
    "    \n",
    "    参数:\n",
    "    state: 状态字典或消息列表，用于存储当前对话的状态和消息。\n",
    "    \n",
    "    返回:\n",
    "    如果最后一条消息包含工具调用，返回 \"tools\" 节点，表示需要执行工具调用；\n",
    "    否则返回 \"__end__\"，表示直接结束流程。\n",
    "    \"\"\"\n",
    "    # 检查状态是否是列表类型（即消息列表），取最后一条 AI 消息\n",
    "    if isinstance(state, list):\n",
    "        ai_message = state[-1]\n",
    "    # 否则从状态字典中获取 \"messages\" 键，取最后一条消息\n",
    "    elif messages := state.get(\"messages\", []):\n",
    "        ai_message = messages[-1]\n",
    "    # 如果没有找到消息，则抛出异常\n",
    "    else:\n",
    "        raise ValueError(f\"输入状态中未找到消息: {state}\")\n",
    "\n",
    "    # 检查最后一条消息是否有工具调用请求\n",
    "    if hasattr(ai_message, \"tool_calls\") and len(ai_message.tool_calls) > 0:\n",
    "        return \"tools\"  # 如果有工具调用请求，返回 \"tools\" 节点\n",
    "    return \"__end__\"  # 否则返回 \"__end__\"，流程结束\n",
    "\n",
    "# 添加条件边，判断是否需要调用工具\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",  # 从聊天机器人节点开始\n",
    "    route_tools,  # 路由函数，决定下一个节点\n",
    "    {\n",
    "        \"tools\": \"tools\", \n",
    "        \"__end__\": \"__end__\"\n",
    "    },  # 定义条件的输出，工具调用走 \"tools\"，否则走 \"__end__\"\n",
    ")\n",
    "\n",
    "# 当工具调用完成后，返回到聊天机器人节点以继续对话\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "\n",
    "# 指定从 START 节点开始，进入聊天机器人节点\n",
    "graph_builder.add_edge(START, \"chatbot\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47bdf00b",
   "metadata": {},
   "source": [
    "\n",
    "### 6. 编译图并可视化\n",
    "\n",
    "使用以下代码可视化构建的状态图：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "8ca50a69-6dcc-4b6d-b9d7-3afedf260d6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 编译状态图，生成可执行的流程图\n",
    "graph = graph_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e6206f1b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "except Exception:\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9f90b11",
   "metadata": {},
   "source": [
    "### 7. 运行图\n",
    "\n",
    "现在，我们可以询问机器人超出其训练数据范围的问题。\n",
    "\n",
    "#### 代码解析\n",
    "\n",
    "1. **`while True` 循环**：\n",
    "   - 这是一个无限循环，用于保持对话的持续进行，直到用户输入退出命令为止。每次用户输入一条消息，系统都会将消息传递给机器人进行处理。\n",
    "\n",
    "2. **用户输入**：\n",
    "   - `input(\"User: \")` 用于从用户处获取输入信息，模拟与机器人对话的场景。\n",
    "   - 如果用户输入的是 `\"quit\"`、`\"exit\"` 或 `\"q\"`，系统将打印 `\"Goodbye!\"` 并退出循环，结束程序。\n",
    "\n",
    "3. **`graph.stream`**：\n",
    "   - `graph.stream({\"messages\": [(\"user\", user_input)]})` 会将用户的输入消息传递给状态图（graph）进行处理，`graph` 会根据流程生成相应的回复。\n",
    "   - `\"messages\": [(\"user\", user_input)]`：这是传递给机器人对话系统的输入消息，表示来自用户的输入内容。\n",
    "\n",
    "4. **遍历 `event.values()`**：\n",
    "   - `graph.stream` 会生成一系列的 `event`，每个 `event` 都包含机器人的响应消息。通过遍历 `event.values()`，我们可以获取每个消息的内容。\n",
    "   - 如果最后一条消息是 `BaseMessage` 类型（机器人回复），则通过 `print(\"Assistant:\", value[\"messages\"][-1].content)` 将机器人的回复输出到控制台。\n",
    "\n",
    "5. **`BaseMessage`**：\n",
    "   - `BaseMessage` 是 LangChain 中的消息类型，用于表示机器人和用户之间的消息。在这里，它用于确认从机器人生成的消息是否符合标准格式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "0ddd8960",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "User:  详细介绍下 LangGraph 项目\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Assistant: \n",
      "Assistant: [{\"url\": \"https://www.langchain.com/langgraph\", \"content\": \"LangGraph - LangChain ... LangGraph\"}, {\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"langchain-ai/langgraph: Build resilient language agents as ...\"}]\n",
      "Assistant: \n",
      "Assistant: [{\"url\": \"https://github.com/langchain-ai/langgraph\", \"content\": \"Overview. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures ...\"}, {\"url\": \"https://github.langchain.ac.cn/langgraph/\", \"content\": \"\\u6982\\u8ff0\\u00b6. LangGraph \\u662f\\u4e00\\u4e2a\\u7528\\u4e8e\\u6784\\u5efa\\u5177\\u6709 LLMs \\u7684\\u6709\\u72b6\\u6001\\u3001\\u591a\\u89d2\\u8272\\u5e94\\u7528\\u7a0b\\u5e8f\\u7684\\u5e93\\uff0c\\u7528\\u4e8e\\u521b\\u5efa\\u4ee3\\u7406\\u548c\\u591a\\u4ee3\\u7406\\u5de5\\u4f5c\\u6d41\\u3002 \\u4e0e\\u5176\\u4ed6 LLM \\u6846\\u67b6\\u76f8\\u6bd4\\uff0c\\u5b83\\u63d0\\u4f9b\\u4e86\\u4ee5\\u4e0b\\u6838\\u5fc3\\u4f18\\u52bf\\uff1a\\u5faa\\u73af\\u3001\\u53ef\\u63a7\\u6027\\u548c\\u6301\\u4e45\\u6027\\u3002LangGraph \\u5141\\u8bb8\\u60a8\\u5b9a\\u4e49\\u6d89\\u53ca\\u5faa\\u73af\\u7684\\u6d41\\u7a0b\\uff0c\\u8fd9\\u5bf9\\u4e8e\\u5927\\u591a\\u6570\\u4ee3\\u7406\\u67b6\\u6784\\u81f3\\u5173\\u91cd\\u8981\\uff0c\\u4f7f\\u5176\\u4e0e\\u57fa\\u4e8e DAG \\u7684\\u89e3\\u51b3\\u65b9\\u6848\\u533a\\u522b\\u5f00\\u6765\\u3002\"}]\n",
      "Assistant: LangGraph 是一个用于构建具有状态的多参与者应用程序的库，主要用于创建代理和多代理工作流。与其他大型语言模型（LLM）框架相比，LangGraph 提供了一些核心优势，包括循环、可控性和持久性。\n",
      "\n",
      "### 项目特点\n",
      "1. **循环**：LangGraph 允许定义包含循环的工作流程，这对于大多数代理架构至关重要。\n",
      "2. **可控性**：开发者能够更好地控制工作流的执行。\n",
      "3. **持久性**：可以维持状态，使得应用程序能够在运行过程中保持上下文。\n",
      "\n",
      "### 应用场景\n",
      "LangGraph 适用于需要多代理交互的应用，如复杂的决策系统、协作任务和动态响应的用户界面。它允许开发者设计复杂的工作流程，并在需要时进行调整。\n",
      "\n",
      "### 资源链接\n",
      "- [LangGraph GitHub 页面](https://github.com/langchain-ai/langgraph)\n",
      "- [LangGraph 官方网站](https://www.langchain.com/langgraph)\n",
      "\n",
      "通过这些资源，您可以深入了解 LangGraph 的功能和使用方法，以及如何将其集成到您的项目中。\n"
     ]
    },
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "User:  q\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Goodbye!\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import BaseMessage\n",
    "\n",
    "# 进入一个无限循环，用于模拟持续的对话\n",
    "while True:\n",
    "    # 获取用户输入\n",
    "    user_input = input(\"User: \")\n",
    "    \n",
    "    # 如果用户输入 \"quit\"、\"exit\" 或 \"q\"，则退出循环，结束对话\n",
    "    if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n",
    "        print(\"Goodbye!\")  # 打印告别语\n",
    "        break  # 退出循环\n",
    "\n",
    "    # 使用 graph.stream 处理用户输入，并生成机器人的回复\n",
    "    # \"messages\" 列表中包含用户的输入，传递给对话系统\n",
    "    for event in graph.stream({\"messages\": [(\"user\", user_input)]}):\n",
    "        \n",
    "        # 遍历 event 的所有值，检查是否是 BaseMessage 类型的消息\n",
    "        for value in event.values():\n",
    "            if isinstance(value[\"messages\"][-1], BaseMessage):\n",
    "                # 如果消息是 BaseMessage 类型，则打印机器人的回复\n",
    "                print(\"Assistant:\", value[\"messages\"][-1].content)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f02dd51-1a42-4b00-bc45-4fb619f55ad0",
   "metadata": {},
   "source": [
    "------------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31f64614",
   "metadata": {},
   "source": [
    "\n",
    "## 搜索引擎工具赋能前后对比\n",
    "\n",
    "下面对比使用 搜索引擎工具前后，chatbot 生成结果对比\n",
    "\n",
    "\n",
    "### 用户问题\n",
    "\n",
    "```shell\n",
    "User:  详细介绍下 LangGraph 项目\n",
    "```\n",
    "\n",
    "### ChatBot + Web Search Tool（整合搜索结果后生成正确回答）\n",
    "\n",
    "```\n",
    "Assistant: LangGraph 是一个用于构建具有状态的、多参与者应用程序的库，特别是与大型语言模型（LLMs）结合使用。它主要用于创建代理和多代理工作流。与其他 LLM 框架相比，LangGraph 提供了一些核心优势，包括循环、可控性和持久性。\n",
    "\n",
    "### 主要特点\n",
    "1. **循环**：LangGraph 允许定义涉及循环的流程，这对大多数代理架构至关重要。\n",
    "2. **可控性**：用户可以更好地控制工作流的执行。\n",
    "3. **持久性**：能够保存状态，使得应用程序能够在不同的会话中保持一致性。\n",
    "\n",
    "LangGraph 的设计使其能够处理复杂的多代理环境，支持用户定义和管理参与者之间的交互。它基于有向无环图（DAG）的解决方案，使得构建和维护复杂工作流变得更加高效。\n",
    "\n",
    "### 资源链接\n",
    "- [LangGraph GitHub](https://github.com/langchain-ai/langgraph)\n",
    "- [LangGraph 官方文档](https://github.langchain.ac.cn/langgraph/)\n",
    "```\n",
    "\n",
    "### ChatBot-only（模型自己编造的 LangGraph 项目描述）\n",
    "\n",
    "```\n",
    "Assistant: LangGraph 是一个旨在推动自然语言处理（NLP）和图形结构数据结合的项目。它通常涉及将语言模型与图形数据结构相结合，以便更有效地处理和理解复杂的关系和语义信息。以下是 LangGraph 项目的一些关键要素：\n",
    "\n",
    "1. **目标与愿景**：\n",
    "   LangGraph 的主要目标是通过将语言理解与图形表示结合起来，增强机器对人类语言的理解能力。这种结合可以使机器更好地处理信息，识别和推理出不同实体之间的关系。\n",
    "\n",
    "2. **图形结构**：\n",
    "   在 LangGraph 中，图形结构通常用于表示实体及其之间的关系。这种结构可以是知识图谱、社交网络图或其他形式的图形数据，能够捕捉到丰富的语义信息。\n",
    "\n",
    "3. **语言模型**：\n",
    "   LangGraph 通常与先进的语言模型（如 BERT、GPT 等）集成，这些模型能够处理和生成自然语言文本。通过将语言模型与图形结构结合，LangGraph 可以在回答问题、信息检索和对话系统等任务中表现得更为出色。\n",
    "\n",
    "4. **应用场景**：\n",
    "   LangGraph 可以应用于多个领域，包括：\n",
    "   - **信息检索**：通过图谱增强搜索引擎的理解能力，提高检索结果的相关性。\n",
    "   - **对话系统**：改善虚拟助手和聊天机器人的对话能力，使其能够进行更自然和智能的交互。\n",
    "   - **知识管理**：在企业和组织中，通过图形结构更好地组织和管理知识。\n",
    "\n",
    "5. **研究与发展**：\n",
    "   LangGraph 还可能涉及大量的研究工作，包括图神经网络（GNN）、知识图谱的构建和更新、以及如何更好地将语言模型与图形数据结合的方法。\n",
    "\n",
    "6. **开源与社区**：\n",
    "   LangGraph 项目通常鼓励开源和社区参与，以促进技术的快速发展和应用。开发者和研究人员可以贡献代码、文档和其他资源，推动项目的进步。\n",
    "\n",
    "总之，LangGraph 是一个前沿项目，旨在通过将语言处理与图形数据结合，提升机器对自然语言的理解和处理能力。这一领域的发展将对人工智能和数据科学产生深远的影响。\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9fbba90-b3d2-4614-99f8-adad9b7e6c0f",
   "metadata": {},
   "source": [
    "------------\n",
    "\n",
    "# Homework: \n",
    "\n",
    "1. 运行和测试第1部分的聊天机器人（ChatBot-Only），并尝试找到一个其无法回答正确的事实性问题。\n",
    "1. 使用联网查询工具（如：Tavily），在第2部分的聊天机器人（ChatBot + Tool）上测试相同问题，并对比生成结果。 "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40e91d18-e480-41ea-95d2-e4f34f419d14",
   "metadata": {},
   "source": [
    "------\n",
    "\n",
    "##"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c82108f5",
   "metadata": {},
   "source": [
    "## 第 3 部分：为聊天机器人添加记忆功能\n",
    "\n",
    "目前机器人可以使用工具来回答问题，但它还无法记住之前的对话内容。为了解决这个问题，我们将为机器人添加记忆功能，让它能够维护对话状态并实现多轮对话。\n",
    "\n",
    "-----------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e230fe85",
   "metadata": {},
   "source": [
    "`MemorySaver` 是 LangGraph 中的一个检查点机制（checkpointing），用于保存和恢复对话或任务执行的状态。通过 `MemorySaver`，我们可以在每个步骤后保存机器人的状态，并在之后恢复对话，允许机器人具有“记忆”功能，支持多轮对话、错误恢复、时间旅行等功能。\n",
    "\n",
    "### `MemorySaver` 的核心功能\n",
    "\n",
    "1. **状态持久化**：\n",
    "   - `MemorySaver` 以内存的形式保存对话或任务的当前状态。在每次状态图（StateGraph）执行时，`MemorySaver` 会记录执行到某个节点的状态，并将状态保存在内存中。\n",
    "   - 在实际应用中，可以替换成 `SqliteSaver` 或 `PostgresSaver`，将状态保存到数据库中，以便在系统重新启动后仍能恢复之前的对话。\n",
    "   - 参考文档：https://langchain-ai.github.io/langgraph/how-tos/persistence/\n",
    "\n",
    "2. **多轮对话支持**：\n",
    "   - 使用 `MemorySaver`，每次调用状态图时，都会将对话的上下文保存下来。当用户重新发送消息时，机器人可以加载先前的状态，继续进行对话，而不会丢失上下文。\n",
    "   - 例如，用户可以在多轮对话中提到之前的内容，机器人能够记住这些信息并做出相应的反应。\n",
    "\n",
    "3. **错误恢复**：\n",
    "   - 在任务执行过程中，如果发生了错误，`MemorySaver` 可以帮助机器人恢复到之前的状态，从而重试任务或采取其他措施来处理错误。\n",
    "   - 它可以让机器人在任务失败或异常时从上一次成功的状态继续执行，而无需从头开始。\n",
    "\n",
    "4. **时间旅行**：\n",
    "   - `MemorySaver` 还支持时间旅行功能。开发者可以回溯到之前的某个对话状态，从那个时间点继续执行不同的分支，这在调试和交互式应用中非常有用。\n",
    "   - 用户或开发者可以选择从某个保存的状态点重新开始，并探索不同的执行路径。\n",
    "\n",
    "### 使用场景\n",
    "\n",
    "- **对话系统**：当用户和机器人之间进行多轮对话时，机器人需要记住之前的消息和上下文，以便提供连续性的回复。`MemorySaver` 能帮助机器人保存对话的状态，并在下次调用时恢复先前的对话。\n",
    "  \n",
    "- **任务执行系统**：在复杂任务执行流程中，如果发生中断或错误，`MemorySaver` 能帮助系统从最近的检查点恢复，继续完成未完成的任务。\n",
    "\n",
    "- **调试与实验**：开发者可以通过保存多个状态检查点，回溯到不同的状态节点，进行调试或探索不同的对话分支和执行路径。\n",
    "\n",
    "### 代码示例\n",
    "\n",
    "```python\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "# 创建内存检查点\n",
    "memory = MemorySaver()\n",
    "\n",
    "# 在编译图时，将 MemorySaver 作为检查点传递\n",
    "graph = graph_builder.compile(checkpointer=memory)\n",
    "\n",
    "# 执行状态图，保存当前对话的状态\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "events = graph.stream({\"messages\": [(\"user\", \"Hi, what's the weather today?\")]}, config)\n",
    "\n",
    "# 在下次调用时，从内存检查点恢复对话状态\n",
    "events = graph.stream({\"messages\": [(\"user\", \"Can you tell me tomorrow's weather too?\")]}, config)\n",
    "```\n",
    "\n",
    "### 总结\n",
    "\n",
    "`MemorySaver` 是 LangGraph 中用于在内存中保存对话状态的机制，支持机器人的多轮对话、状态恢复和时间旅行等功能。它通过检查点机制，使机器人可以在任务或对话的不同阶段保存并恢复状态，从而提高对话系统的连续性和容错能力。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2918a620",
   "metadata": {},
   "source": [
    "### 1. 创建 MemorySaver 检查点\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "f1093d11",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "# 创建内存检查点\n",
    "memory = MemorySaver()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "890ecf8c",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "718e277e",
   "metadata": {},
   "source": [
    "### 2. 使用检查点编译图\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b436f151",
   "metadata": {},
   "source": [
    "在编译图时，我们将提供检查点功能，机器人可以在每个步骤中保存状态。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "eaf106fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "graph = graph_builder.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "da903750-f00b-4208-8bb4-460a768843c9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b0b3e5a",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a04a076a",
   "metadata": {},
   "source": [
    "### 3. 执行聊天机器人\n",
    "\n",
    "我们仍然按照之前的方式执行机器人，只是这次它会在每一步保存对话状态。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fb3dfde",
   "metadata": {},
   "source": [
    "\n",
    "#### 代码解析\n",
    "\n",
    "1. **config 配置**：\n",
    "   - `config` 是一个包含对话线程信息的字典，用于指定本次对话的唯一线程 ID。在这个例子中，`thread_id` 被设置为 `\"1\"`，这意味着该对话会与该线程关联。\n",
    "   - 每个线程 ID 代表一个独立的对话流，使用 `MemorySaver` 时，机器人可以在同一个 `thread_id` 下记住对话的上下文。\n",
    "\n",
    "2. **用户输入消息**：\n",
    "   - `user_input` 表示用户的输入消息，在这里用户输入了 `\"Hi there! My name is Peng.\"`。这个消息将作为对话的起始消息传递给机器人。\n",
    "\n",
    "3. **graph.stream**：\n",
    "   - `graph.stream` 是用于执行状态图的函数。它会根据传入的消息和配置执行对话流程。\n",
    "   - `{\"messages\": [(\"user\", user_input)]}`：第一个参数包含用户的输入消息。消息格式是一个元组，`(\"user\", user_input)` 表示这是来自用户的消息。\n",
    "   - `config`：第二个参数指定对话的线程配置，这里使用 `{\"configurable\": {\"thread_id\": \"1\"}}`，表示这次对话属于线程 ID 为 `1` 的对话流。\n",
    "   - `stream_mode=\"values\"`：设置流模式为 `\"values\"`，表示返回消息中的内容数据。\n",
    "\n",
    "4. **遍历事件并打印消息**：\n",
    "   - `for event in events` 遍历每个从 `graph.stream` 中返回的事件，`event[\"messages\"][-1]` 获取每个事件中的最后一条消息。\n",
    "   - `pretty_print()`：调用消息对象的 `pretty_print()` 方法来格式化并打印消息的内容。这通常用于输出对话中的 AI 响应或工具调用结果。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "6a3d3d84",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Hi there! My name is Peng.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Hello Peng! How can I assist you today?\n"
     ]
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# 用户输入的消息\n",
    "user_input = \"Hi there! My name is Peng.\"\n",
    "\n",
    "# 第二个参数 config 用于设置对话线程 ID\n",
    "# 在这里，\"thread_id\" 是唯一标识符，用于保存和区分对话线程。\n",
    "# 每个对话线程的状态将由 MemorySaver 保存下来，因此可以跨多轮对话继续进行。\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},  # 第一个参数传入用户的输入消息，消息格式为 (\"user\", \"输入内容\")\n",
    "    config,  # 第二个参数用于指定线程配置，包含线程 ID\n",
    "    stream_mode=\"values\"  # stream_mode 设置为 \"values\"，表示返回流式数据的值\n",
    ")\n",
    "\n",
    "# 遍历每个事件，并打印最后一条消息的内容\n",
    "for event in events:\n",
    "    # 通过 pretty_print 打印最后一条消息的内容\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b24b166",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2a5dee8",
   "metadata": {},
   "source": [
    "在下次执行时，机器人将记住之前的对话内容：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "95c7f93d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Remember my name?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Yes, I remember your name is Peng! How can I help you today?\n"
     ]
    }
   ],
   "source": [
    "user_input = \"Remember my name?\"\n",
    "\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},\n",
    "    config,\n",
    "    stream_mode=\"values\"\n",
    ")\n",
    "\n",
    "for event in events:\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06a52aae",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a53f7e73",
   "metadata": {},
   "source": [
    "通过这两步操作，您已经成功为机器人添加了对话记忆功能，它可以记住之前的交互，并在后续的对话中做出相应的回应。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "8b8df867",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "Remember my name?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "I don't have the ability to remember personal data or previous interactions. However, I'm here to help you with any questions or information you need! How can I assist you today?\n"
     ]
    }
   ],
   "source": [
    "# 机器人仅记住同一个线程中的历史对话，尝试切换到线程2\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},\n",
    "    {\"configurable\": {\"thread_id\": \"2\"}},\n",
    "    stream_mode=\"values\",\n",
    ")\n",
    "for event in events:\n",
    "    event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae90b143",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f023df43",
   "metadata": {},
   "source": [
    "检查点让机器人可以记住对话状态，您可以通过 `get_state` 函数查看机器人当前的状态：\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "bb8b2464",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StateSnapshot(values={'messages': [HumanMessage(content='Hi there! My name is Peng.', id='3fdc96df-3642-4502-b27e-bcd7d68c205f'), AIMessage(content='Hello Peng! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 11, 'prompt_tokens': 87, 'total_tokens': 98}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-a94b40f6-7943-4ffe-9a06-523716ffd4a1-0', usage_metadata={'input_tokens': 87, 'output_tokens': 11, 'total_tokens': 98}), HumanMessage(content='Remember my name?', id='72324598-e56a-4aae-8e70-50a8cf399183'), AIMessage(content='Yes, I remember your name is Peng! How can I help you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 109, 'total_tokens': 126}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-fdf94b39-a06e-449d-962b-d572b9f3d6b2-0', usage_metadata={'input_tokens': 109, 'output_tokens': 17, 'total_tokens': 126})]}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6de7e-9d58-68ce-8004-2b6ef0769a7d'}}, metadata={'source': 'loop', 'writes': {'chatbot': {'messages': [AIMessage(content='Yes, I remember your name is Peng! How can I help you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 109, 'total_tokens': 126}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_483d39d857', 'finish_reason': 'stop', 'logprobs': None}, id='run-fdf94b39-a06e-449d-962b-d572b9f3d6b2-0', usage_metadata={'input_tokens': 109, 'output_tokens': 17, 'total_tokens': 126})]}}, 'step': 4, 'parents': {}}, created_at='2024-09-08T13:40:29.973306+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef6de7e-9928-62ed-8003-bcaa00c55c55'}}, tasks=())"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "snapshot = graph.get_state(config)\n",
    "snapshot"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b33358b-945d-419e-acaa-11cb5eaccdd9",
   "metadata": {},
   "source": [
    "### 使用 Pandas 表格呈现状态\n",
    "\n",
    "对于其中的 messages 等结构化数据，可以将其转换为 Pandas DataFrame 进行展示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "6e7e6291-acef-44f5-97f6-fd542d16cc86",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>content</th>\n",
       "      <th>message_id</th>\n",
       "      <th>type</th>\n",
       "      <th>token_usage</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Hi there! My name is Peng.</td>\n",
       "      <td>3fdc96df-3642-4502-b27e-bcd7d68c205f</td>\n",
       "      <td>HumanMessage</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Hello Peng! How can I assist you today?</td>\n",
       "      <td>run-a94b40f6-7943-4ffe-9a06-523716ffd4a1-0</td>\n",
       "      <td>AIMessage</td>\n",
       "      <td>{'completion_tokens': 11, 'prompt_tokens': 87,...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Remember my name?</td>\n",
       "      <td>72324598-e56a-4aae-8e70-50a8cf399183</td>\n",
       "      <td>HumanMessage</td>\n",
       "      <td>None</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Yes, I remember your name is Peng! How can I h...</td>\n",
       "      <td>run-fdf94b39-a06e-449d-962b-d572b9f3d6b2-0</td>\n",
       "      <td>AIMessage</td>\n",
       "      <td>{'completion_tokens': 17, 'prompt_tokens': 109...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                             content  \\\n",
       "0                         Hi there! My name is Peng.   \n",
       "1            Hello Peng! How can I assist you today?   \n",
       "2                                  Remember my name?   \n",
       "3  Yes, I remember your name is Peng! How can I h...   \n",
       "\n",
       "                                   message_id          type  \\\n",
       "0        3fdc96df-3642-4502-b27e-bcd7d68c205f  HumanMessage   \n",
       "1  run-a94b40f6-7943-4ffe-9a06-523716ffd4a1-0     AIMessage   \n",
       "2        72324598-e56a-4aae-8e70-50a8cf399183  HumanMessage   \n",
       "3  run-fdf94b39-a06e-449d-962b-d572b9f3d6b2-0     AIMessage   \n",
       "\n",
       "                                         token_usage  \n",
       "0                                               None  \n",
       "1  {'completion_tokens': 11, 'prompt_tokens': 87,...  \n",
       "2                                               None  \n",
       "3  {'completion_tokens': 17, 'prompt_tokens': 109...  "
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# 将消息内容转换为 DataFrame 显示\n",
    "messages = snapshot.values['messages']\n",
    "df = pd.DataFrame([{\n",
    "    'content': msg.content,\n",
    "    'message_id': msg.id,\n",
    "    'type': type(msg).__name__,\n",
    "    'token_usage': msg.response_metadata.get('token_usage') if hasattr(msg, 'response_metadata') else None\n",
    "} for msg in messages])\n",
    "\n",
    "df  # Jupyter 会自动渲染 DataFrame"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2128bcb",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16c557f8",
   "metadata": {},
   "source": [
    "---\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15417aa1",
   "metadata": {},
   "source": [
    "## 第 4 部分：引入人类审查\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55389548",
   "metadata": {},
   "source": [
    "有时机器人可能需要人类的介入来做出复杂决策，或者某些任务需要人类批准。LangGraph 支持这种“人类参与”的工作流，您可以在对话中加入人类审查。\n",
    "\n",
    "第4部分，主要展示了如何通过 LangGraph 进行多轮对话和工具调用，并且能够手动更新对话状态、插入工具调用的结果，之后继续执行对话。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21516923",
   "metadata": {},
   "source": [
    "### 1. 编译图时添加中断\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22974e2c",
   "metadata": {},
   "source": [
    "我们可以通过 `interrupt_before` 参数，在工具节点执行之前中断对话，让人类有机会审查。\n",
    "\n",
    "\n",
    "**编译状态图**：\n",
    "   - `graph_builder.compile()` 用于编译状态图，生成可执行的 `CompiledGraph` 对象。\n",
    "   - `checkpointer=memory` 使用内存作为检查点保存对话状态。\n",
    "   - `interrupt_before=[\"tools\"]` 表示在执行 \"tools\" 节点之前中断，允许手动干预。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "6a9dc7e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 编译状态图，指定在工具节点之前进行中断\n",
    "graph = graph_builder.compile(\n",
    "    checkpointer=memory,  # 使用 MemorySaver 作为检查点系统\n",
    "    interrupt_before=[\"tools\"],  # 在进入 \"tools\" 节点前进行中断\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "68c4a8ca-67cc-44a5-8ee3-3bb4167ae07b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAEjARADASIAAhEBAxEB/8QAHQABAAMBAAMBAQAAAAAAAAAAAAUGBwQBAwgCCf/EAFMQAAEEAQIDAgcKCgcECgMAAAEAAgMEBQYRBxIhEzEUFRYiQVaUCBc2UVR0k7LR1CMyNWFxdYG00tM0QlJVkaGxM3OzxAkkJSZDRFdiZHKSovD/xAAaAQEBAQEBAQEAAAAAAAAAAAAAAQQCAwUG/8QAMxEBAAECAgULBAIDAQAAAAAAAAECEQMSBDFRUtEUITNBYXGBkaGxwQUyYpITIiPh8NL/2gAMAwEAAhEDEQA/AP6poiICIiAiIgIiICIiAiIgIiICIo/NZhmGrMf2UlqxM8RQVodueaQ9zRv0HQEknoACT0C6ppmqbQJBR0uo8TA/llylKN3xPsMB/wBVEeRTc5+G1NN42e7r4ACW0oh/ZEfdJ/8AaTcnrsGg8oko9I4KFnLHhccxu+/K2rGB/ovbLhU80zM93/fC8z9eVWF/vih7Sz7U8qsL/fFD2ln2p5K4X+56HszPsTyVwv8Ac9D2Zn2J/h7fReZ7INQ4u08MhyVOZ57mxzscf8ipBQ82jsBZZyS4PGyt/svqRkf5hR50pLp9vbabldXDB1xU0hNSXr3DcExH0As80elrky4VXNTMxPbx/wBJzLQi4cNl4c3RbZhbJEdyySCYcskLx0cx43OzgfiJB7wSCCe5eExNM2lBERQEREBERAREQEREBERAREQEREBERAREQEREBERAVXx+2X15lbD9nR4iGOlA3+zLI0Syu+LqwwAekbO/tK0KsaeHgWsdVVXgh1mWvkWbjoWugbD0Pp2Nc7/FuPjC0YX21z2fMfCx1pzLZangcVcyWRsxUsfThfYsWZ3BrIo2NLnPcT3AAEk/mWKan92NoGpwo1XrXTN92pWYGKMuqirZriSSXmELeZ8O4a8td54Bb0PVarxDx9HK6B1JSyeKsZzH2MdYisYyoN5rcZjcHRR9R57hu0dR1I6hfIGldMcRdVcIOLuh8bi9Wy6Hbp9tbS8Gt6LaeUZOGHeozuMkYADWud0HmgHbdZ0b/hvdUaAs8KsfrvJZeWhip546EzzjbZLLhiEjoms7LneAN9nhpadu9SeQ90xwyxegMVrexq2q3SuUutx1XJMhlew2CHHs3hrC6MgMfuXhoG3UjcLA9U6717qbhzwwr47SvEvSumaEpxuqocPjDBm3GKpH2Lq7er+wMpc10jdjs093TfP9JcL9WDQeFxFzRuqGsbxwp5s18xUksTjFyRg9vPIA5rgBuJH7kBxPMQSg3rWXu5NC6a1VoWlWktXMHqA3H28o/G3Y3VI4Wua0shMHPKXytLDy/ihpJGxBX0XUtR3asNiFxdDMxsjCWlpLSNx0PUdPQV8+e6YrZrA8VuDWvqGmczqnE6Zt5NmRq4CobVtgs1RFG9sQIJAIO59H7Qt+xV45TF07hrT0zYhZN4NaZySxczQeR7dzs4b7EeggoIJu2I4g9nHs2HM03zvaN+s8BjZzfFu6ORo/REFZ1WMg3wziHh427nwKjYnkO3Qdo6NjBv8An5ZP/wAVZ1oxdVEzrt8zb0ssiIizoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAoTP4mxJaqZbHNY7J0w5gjkdytnhcRzxE+gnlaWn0OaPQSptF3TVNE3hdSOwufpZ6F76sv4WI8k9aQcs0D/AOzIzvaf0946jcEFSKiM1pPF5+aOe3WItxt5Y7leR0Fhg79myMIcBv6N9lHnREoG0epc7E3ffbwljv8ANzCf8165cKrniq3fxjgcyzos14jYbJ6W4e6ozVHVOZN3HYu1cgE0sJZ2kcTnt5vwY6bgb9QpjF6Rt3MZUsSaqzvaSwskdyyw7bloJ/8ACT+PD3/SS0bVyUZmdQ1MII45Oexdm6V6NcB085+JrSR0+NxIa3vcQOqivId7thLqTPTNH9XwprN/2sY0/wCalMLpnG6f7R1KtyTSACSxK90s0nxc0jyXu9PeT3pbCp55m/8A23/RzPVpzDzUBau3zG/LX3CSyYSTGzYbMiYTsS1g6b7DclzthzECZRF41VTXN5TWIiLkEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREFM40kDg5rsuJDfEN/cj5u/wDOP9QrJgPyFjvm0f1Qq3xp395zXe2wPiG/+MAR/R39+/T/AB6KyYD8hY75tH9UIO9ERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBS+NY34Na9BcGjxBf85w3A/6vJ1KsuA/IWN+bR/VCrXGzb3mte79B4gv77Df/AMvJ6PSrLgPyFjfm0f1Qg70REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBEUXqHPR4CpG8xPs2Z39jWrR9HTSEE7b9wAAJJPcAT+ZdU0zXOWnWJRFSXZ7V7iSMdhGA/1Tdmdt+3shv8A4Lx491h8gwftc38tauS17Y84Wy7oqR491h8gwftc38tPHusPkGD9rm/lpyWvbHnBZk/u4OPdjghw0EPktLnsfqWvcxM11lsQtoyPh2j5mmN4fzB0h26f7P079J/3JnHy97oTQVjOSaTfpnF05WUassl4WDccxv4RwHZs5Wt80b9dyXDpy9fPGvQGa448Nsvo/M0sLDWvMBjtMsSufWlad2SNBj7wR+0Ej0qS4Z6bznCrQWD0nhsbhG47FVm143OtTc0h73Pd+D/Gc4ucfzkpyWvbHnBZrqKkePdYfIMH7XN/LTx7rD5Bg/a5v5aclr2x5wWXdFSPHusPkGD9rm/lr9Mz+rmHmfjMNK0d7GXZWk/oJiP/APf4pyWvbHnBZdUUdgs3Bn8eLULXxEPdFLBKAHwyNOzmOA6bgjvBII2IJBBMistVM0zNM60ERFyCIiAiIgIiICIiAiIgIiICIiAiIgIiICputT/3o0mPR21k/t7E/aVclTNa/CnSf+9s/wDBK16L0vhPtKwkERFoQRQ+odXYnSsuIjylvwV+WvMxtIdm9/a2Hte9rPNB5d2xvO7th07+oUwoCLlymVp4PG2sjkbUNGhVidNPZsPDI4o2jdznOPQAAEklR2Q1rhcXk9P4+zeDLefkfFjWNje4WHMidM7ZwBDdo2Odu4gHbYdeiCbRFxZnNUNOYq3k8pcgx+OqRmWe1ZkEccTB3uc49AFR2ovDXB7Q4HcEbgryg4uHp3tasHoGXOwA/wDi1z/qSrgqdw8/pmrf1x/ytZXFZtJ6WfD2hZERFlQREQEREBERAREQEREBERAREQEREBERAVM1r8KdJ/72z/wSrmqZrX4U6T/3tn/gla9F6Xwn2lYSCyPj7nMpHe0DpfH5ezp2tqfOeAXcrTcGTxxNgll7KJ5HmPkdG1gcOo67dVrigtaaGwPETAyYbUeNiymOke2TspSWlr2ndr2OaQ5jh6HNII+Ne888Iw3jVw4bgcTw0wVfU2o52W9eVHC/eyTrNyuDUsgtimkBcB0JBO5BcSCOm0Xc1RZ0LFxQ0VczuqszWq5bEUsE+vkv+1XTXY2OFZtuTq1peHee47tY52x3AW0YjgfovB06FapiJBHRyjM1A6a9YmkFxsZibK575C55DHFuziRtt06BdOd4P6Q1K7ULsnhm2n599aTIOdPK10j64Age0hwMbmADZ0fKd+u+64yyPmXKHUsnCb3ROjdTXMmIsHh4L9OKbOy5CxAJa0shjdbLI3yMJhBLHAjZzmkuaett1dw+qtyPAPBQZrPx17l+3M6943nkuM3xcri2Od7nPY07bbNI2BO23etm05wS0TpQ5Y43BsjOXqClkjNYln8Oi8/pP2j3dq7aR4537u2O2+3RRY9zZw7GCp4c4Od1ClYdaqsflLbn15CwR80bzLzs2Y0ABpAG3TZTLIxPKav1XpnJZ/htW1blbWMbrLD4SLUlmcSZCpVu1zNLD25HWRrmCNr3buHbD0gbRvHOpcwOmONGgfKDOZzBU9K1c9XkyGRlnsU53SysfA6YnnfG8RtfyPJHf6CvpOpwS0PR0Nb0fFp2t5PXJDPZqyOe900pId2r5XOMjpN2tIeXcw5RsegX705wZ0ZpXBZrD0MHGaOaaWZIW5pLUlxpYWcsssrnPeA0kAF3QE7bJlkTOitOVtK6dq0KlzIX4AO0E+Tvy3Zjzdf9rK5ziPiG+wHcpxV7Q2gcJw4wnijAVpquP7QyiKe3NZIPK1vR0r3OA2a0BoOw26BWFekDh4ef0zVv64/5Wsriqdw8/pmrf1x/ytZXFZ9J6We6PaFkREWVBERAREQEREBERAREQEREBERAREQEREBUzWvwp0n/AL2z/wAEq5qt60oNsRY61HarVshVs71G25ezjsPc1zDCT3+cHHbYEggHZ22x0aPVFGJEz2x5xMLDyir1HUGdvUoLDdFZdjZWB4bJNVjcAe7dr5muafzOAI7iAV7vG2e9TMr7VS/nrfk/KP2jiWTaKE8bZ71MyvtVL+enjbPepmV9qpfz0yflH7RxWybRUvWPEabh/pfI6i1BprI43DY6IzWbUlio4Mbvt3NmJJJIAABJJAC6sBrO/qjCUMxi9LZK5jb8DLNawyzT2kjcA5rus+46HuPVMn5R+0cSy1IoTxtnvUzK+1Uv56eNs96mZX2ql/PTJ+UftHEsm0UJ42z3qZlfaqX89fpmS1BKeVukb8Tj3OsW6oZ+0tlcf8AUyflH7RxSzr4ef0zVv64/5WsriqZp25HpAZGDOHwB8tmGaTJ2Hsjp2ZpyGMjhcXbgtc1kYa4AklmwPMFc1gx6orxJmnVwixIiIs6CIiAiIgIiICIiAiIgIiICIiAiIgIvTbuQY+s+xamjrwM6uklcGtb6OpKhR4y1HM0ntsRi45LNeevIzlsW2gcjHskY/eJm/O4bDnP4M7s6tIfrM560X3cdgq8dzNwwxygW+0jqsD38o5pQ0gkAPdyN3ds0b8oc0rpraegjvyXbUkmRs+EusV3Wg13gm8Yj5YdgOQcu4J7zzv3PXZdeMxlXC46rQo146tKrE2GGCIbNjY0bNaB8QAC6kBERAREQfMvu+eGOt+KXB91LTGTxeOweNbPls22/NLHLPHBHzxsj5I3B3c8kOLRuGdfisfuN+GeueEPCSDS+s7+JyUNaTtcXNjLEshZBIOYxvD4mbcriSNt/xj3bBaDxumjr8GNeySlojbgL5dzkgf0d/eR1/wAFa8TA6ri6cLxs+OFjHD84aAg60REBERB67FeK3A+GeJk0Mg5XxyNDmuHxEHvUO3C3sbfikxl4eBzW5LF2teMk5c17QNoXF34LZ4DttnN854AG4LZxEEPhdTV8t2EE0UmLyskLp3Yq65gsxsa8sc4ta5wLeYbczSWndp36hTC4cthq+ZrSxTc8Ur4ZIWWq7uznhDxs4xyDqw9Adx6QPiUZ41uadkMeXcLGN561arkWAvnkkf5jjOxkYazzw087dm/hNi1gZuQsKJ3ogIiICIiAiIgIiICIiAiIgL8yyCKN73Bxa0FxDWlx6fEB1P6Av0q3qem3NZnCYqzSq3Mc6R1+YT2C17XwOjfCWxg/hNpCx3Xo0tae8tQeyjRk1GY8jla0sVZ7YJa+HvRRE1ZWOc8Su5S4GTcsP4xDTG0jZ25NgREBERAREQERReptSUdI4O1lslI6OpXDdxGwvkke5wayONg6ve97msawblznNABJCCpcV3HUL8HoqueafN2mzXA09YsfA9slh5677PPZwfpsD0A7aCqjoXTt+vNf1Bn2tbqHKlvaQMfzso1279lVYe48u5c9w/Gke8jzeUNtyAiIgIiICIiAiIgrMoi0HA+cPbBpmJs09kyuklfWe6QOBb37QjmkJHQRgN22YDy2UEOAIO4PcQvKgdPGfHZDIYeUZOzFAfCocheLXskZK+Q9ix42J7Lbl2cOblLNy4klBPIiICIiAiKFzGttPaftCtk85jsfZI5uxs2mMft8fKTvsu6aKq5tTF5W100iq3vpaO9acR7bH9qe+lo71pxHtsf2r15Pjbk+UrlnYtKKre+lo71pxHtsf2p76WjvWnEe2x/anJ8bcnykyzsWlFVvfS0d604j22P7U99LR3rTiPbY/tTk+NuT5SZZ2LSqVr/PYbR2a01nc3YwuMpslnovy2YvMqGs2SIv5Yy8gPL3QMBb37Dm/qldnvpaO9acR7bH9q+C/wDpA+B+F4g6locQND5bHZLL3ZIaOYo1rbHyP2AZFZA37mtDWO+IBp9DinJ8bcnykyzsf0NwOoMXqnE18phclTy+MsAmG7QnZPDKAS08r2kg7EEdD3gqQWacOc/oPh1oLT+mKWqMMK2JoxU2ltyMc5Y0Au7+9x3P7VYvfS0d604j22P7U5Pjbk+UmWdi0oqt76WjvWnEe2x/anvpaO9acR7bH9qcnxtyfKTLOxaUVW99LR3rTiPbY/tT309HetOI9tj+1OT425PlJlnYsV69XxlKxcuTx1aleN0s08zwxkbGjdznOPQAAEknu2VL0/jrWucvV1RmastTH1iZMHirLHRyxgtLfC543bFszmkhrHDmiY482z3vayswcQtM8Qsw25ls9jaemqE4fSxlqzG192ZjgW2Z2E7taxzQYoz6QJHDcMDLx76WjvWnEe2x/anJ8bcnykyzsWlFVvfS0d604j22P7V7IeJmkbEgZHqbEPcdgALsfpOw9PxkD9qcnxtyfKUtOxZURFnQREQEREBERAVez1V0ep9N5GGhauSiWejLLBY5I60EkRkdJIw9JB2kELB6QX7jpzb2FV3WlLwuHDuGNsZN0GUrStbXn7Iw+fsZnf2msBJLfSEFiREQEREHFmrjsfh71pgBfBBJK0H42tJH+iqOkqkdbAUpAOaezEyeeZ3V80jmgue4nqSSf2d3cFZ9VfBjMfM5vqFV7TXwcxXzSL6gX0MDmwp716kkiIu0EREBERAREQEREBERAREQEREBeHsbIwte0OaRsWuG4K8og5OHbxBDnMZGSKmMyJrVo9ukUboIZgxv/taZSAO4AAAAABW5U7h9+U9Z/rhn7jUVxWbSelnw9YhZ1iIiyoIiICIiAq5rioy3TxTX4+5keTK05AynJyOiLZmkSv8AjY38Zw9IBCsarutqz7VTFhlC3fLMpUkLak/ZGMCZpMjj/WY3vc3+sAQgsSIiAiIgi9VfBjMfM5vqFV7TXwcxXzSL6gVh1V8GMx8zm+oVXtNfBzFfNIvqBfRwehnv+F6kkiIukEREBERBknukta6q0RpnTdjSkMElq5qPG0Z+3siHmjksMb2W5jfsJCeQuA3aHEjcjZezOcX9SU9Q47SmI0XBmtYuxYy2SpNzAhqUYS8xtHhDod5HOc1waOzb0aSeUKa416ByXETRkVPC2qtTNUMlTy1F94ONd01edkrWScvnBruUgkAkb77HuVTv6E4kVdYVddYcaWbqa5ifFGXxduxZ8BLY53yQSwzNi5y5okcHNcwA79CNt1xN7j8UvdJSasr6ap6P0rNmtTZetatz4q7dbTZjY603YT9vLyv6ib8G0NaebbfoFE2OLtrXWpuEs9MX9PzP1RkcRm8ObB8yeCnZ54ZCw8srQ9rXtPcfNOwPd+NOe5+1Xwxm05n9K5PD5bVcFO9UzLcz2tere8Ks+FPfG6Nr3Rlku4aC07tOx2PVe2n7n7U+GxmmsrUy+Kt6yp6qtaoyJsMkjpWH2o5Ipoo9t3tDY5AGEg7lnUDfpz/brFt4X8ZMnxP1FmIqul46mnsddtY92RflY3WmzQyFhEtXkDouYglvnE7bEgbhamsUxPCfVtrjpR1vlY9M4ivRbchknwHbi3l4ZBywR22uaG/gxs7fmf5zenKOi2tdxfrBERdAiIgIiIOHh9+U9Z/rhn7jUVxVO4fflPWf64Z+41FcVm0rpPCPaFkREWVBERARFUdZcTcVo+TwVzZchky0OFKqAXNB7i9x2awfpO567A7L1wsKvGqyYcXkW5fH/uvvdg2eBGuMbpzLcOZM1jHGvl8dlYM8aondG8EtcwV3bcsjdi3mO7eUnbm2Wp2ON+opnF0GJxlVp7mSzSTEfpIDf9FlHHfT590Lj8DU1NQxrRh77bsMlYSB7m9O0hJJPmPAbvt180bHovrR9H0ueqPOF8X0nwf1zkOJfDPT+qcpgjpq3lq/hXix1nwgxRucezPacjN+ZnI/8Ubc23XbdXFYTBxk1NXiZFHj8LHExoa2NjJWhoA2AA5ugUziOOs8crW5rChkHps46UylvX0xloO3p80k/m+Pmv6RpdMXy37pgs11FyYrK1M3j4b1Cwy1UmG7JYzuD12I/MQQQQeoIIPULrXx5iaZtOtEXqr4MZj5nN9Qqvaa+DmK+aRfUCsOqvgxmPmc31Cq9pr4OYr5pF9QL6GD0M9/wvUkkXovVTdo2K4mkrmaN0YmhdyvZuNuZp9BHeCo7yYg+WZL26X+JWZlEwih/JiD5Zkvbpf4k8mIPlmS9ul/iUvOwTCKH8mIPlmS9ul/iTyYg+WZL26X+JLzsEwih/JiD5Zkvbpf4k8mIPlmS9ul/iS87BMIofyYg+WZL26X+JPJiD5Zkvbpf4kvOwTCKH8mIPlmS9ul/iTyYg+WZL26X+JLzsEwih/JiD5Zkvbpf4k8mIPlmS9ul/iS87BMIofyYg+WZL26X+JPJiD5Zkvbpf4kvOwTCLno0WY+ExMkmlBPNzTyukd/i4kroVHDw+/Kes/1wz9xqK4qncPvynrP9cM/caiuKz6V0nhHtCyIiLKgiIgqvEjVztHaadZrhrshYkbWqNf3do4E8x+MNa1ztvTy7elYEA4ue+SR880ji+SaV3M+Rx73OPpK0Tj1K/x1paHf8CYrkpHo5x2Ab/k96zxfvPo+DTh6NGJGuq/pNvgnUIvk2/nOI/ELUOt72DOoPCcPlZ8fj2Y3M1alKt2R2aJ4JXB0vN3knod9h3bC2x0NR694q5bD5bUmc06+DTlG5LSw98xMiuOBD9iNxsHF3Rp2dsN99gtUabm+2iddo6r6+Dl9CqE0/rLGanyObpUJHyT4ez4Hb5mFobJyh2wJ7+h71884PiBqPiRp7g/g7mobmDbqOK6/IZWhIIbNg1i5rI2P281ztgSR1O/7Ddfc5YrxHneJtDw21kfB892fhV6XtJ5Nom9Xu2HMfzq0aX/LXTFEc0/+b/MD6L0BquTRuooiXkYrITNiuRE+a1581kwHocDytcfS3v35G7fQ6+TM2AcNf36DsHnc+jzT1X1Ti5ZJ8ZUllG0r4WOePzlo3XwPrmDTTVRixrm8T4WddTl1V8GMx8zm+oVXtNfBzFfNIvqBWHVXwYzHzOb6hVe018HMV80i+oF8jB6Ge/4XqSSIi6QRVPivrV/DjhtqTU8dV9yTF0ZLLYo2tcSQOhLXPYHAd5AcCQCG7uIBqWd90FS0d4xr5bA5q5Pg69OTN3MbWi8FpmdgId58wcQCerWhzwOuxHVczMQNZRZhS4xWDxD1zi8hhJ8fpXS8ETrOfkkg7KOTsDYlMn4bn5eydCW8sZPV3Ny9N4+f3TeAo47KXb+B1DjoqeJ8dwMs1oRLeqmRsbXRMEpc1znPYAyURuO/d0OzNA19FnmV4xsw93BY+fSOonZfNvsijjY2VTM9kDGPdI49vyRtIe0Dnc0gnZwaSF5l424aDA5fKyUMk2PG5yHTz4BHGZZrckkEQEe0mzmh9gNJJHVj9gdhuvA0JFmEHugMRNl4Kz8FnYMbNnJdOszUkEPgZusmfDydJTJyukYWh/Jy7kAkHcDr4Q8S8rxJbqCzc03aw+OqZSzToW5ZIHMsxwymF/4kz3c4kjl3PKG7cvKXdSl4GiIqvPxS0XV1AMFNq/AxZwzNrDGSZOFtkyuIDY+yLubmJIAbtudwq/xv1xldFY7SrcLHZnyGV1DTo+D04o5Jp4RzTTxsEmzQXRQyN5iW8vNvzN23FvA0hFl8PuhMFbo0hUxOat5+1ds49um44IhfZNX2M4fzSCJrWBzCXmTkIezZx5gq3qXjXkdZ+97Q0XVzFJmqp7Uk+QgipOs1K9bmbMGNnkMfOJezBds9vIXFvOS0KZoG6Is2xXHPC5HM4qlFj8u7F5O9Ji6Go5IYhRuWo2yFzGEP7Tr2UgDzGGOLfNcdxvyY33RGEyOkYNTeI87Ww92VlbGSTQQmTJzvkdGyKvE2UvLnFpILg1vL53NsCQzQNURVbQHECrxAqZWSHHXsTaxd52Ou0sh2RlhmbHHIRvFJIxw5ZWHdrj3kHYghWlUcPD78p6z/AFwz9xqK4qncPvynrP8AXDP3Goris+ldJ4R7QsiIiyoIiIKBxl01NmtOQXqkTpreLm8I7Ng3dJEWlsjR+wh+3pLAPSsXY9srGvY4PY4bhzTuCPjC+p1lOteDss1mW/pt9eF0h5pMbP5kTnel0bgDyE9fNIIJ9LepP6f6V9Qowaf4MabR1T8Gt86Z/gZoXVGonZ3Jaehnyj3Ne+dkssYkc3uL2scGvPQdXA9yscGkcTW1Nd1BHU5cvcrsqz2e0eeeJp3a3l35Rtv3gbq02NKampuLZ9M5Jrh39k1kw/YWOK9PiHPereX9lP2r9NTVo33UzTz8+uEyyzy3wR0Re0dj9LWMDHLg8e90lSu6aXnhc5xc4tk5ucblx/rf6BSmieHGneHNa5X07jRjYbcomnaJpJOd4G2/nuO3QehW8YHPk7eTeX9lP2qWxHDbVWbla3xZ4ogPfZyD2nbr6I2OLifzHl/SuJr0TC/vM0xbuvxMsojDYCTVucp4WJpLLDuay5p27Ou3rI4/p/EH53j86+mwABsOgVf0ZomhoqhJFV5prU5DrNyUDtJiN9h+Zo3OzR0G57ySTYV+M+pabGmYkZPtp1fMuuxF6q+DGY+ZzfUKr2mvg5ivmkX1ArDqr4MZj5nN9Qqvaa+DmK+aRfUC8cHoZ7/g6nXesSVKVieKtLdlijc9laAsEkpA3DGl7mtBPcOZwG56kDqqj5f53/011R7TivvquqKozXVePyXGDTN/S93T2Z0jVsmCSW9kPAp45GR2InvhDYLT3byMa5u5GwBJ6kBp9Ge4I+P8drSrNmuV2qM5TyliXwXcx164qt8FA5+oc2sRz+jtSeU7ddRRS20ZTa4HTZKrxLxN7Ptn07rV000ldlLkt1JpIIoS4T9oWva1sTeVpjG3pJAXFjPc9x1NFyYCSfT9Iz5ShetT6f04zGttRVrEc3ZSMbK7dzzGQX77AOOzPQtjRMsCsWdE+F8S8fq2S5zCjibGMgpGL8UzTRSSS8/N3kQMbty/H19Cz73hsnWvVzLqtkunamqZtWeLo8STZnkdLJOIXzdseYNke0tLYwdmAEHoRtCJaBgXBzhFqHI6O0Rb1flRHSqT+UTNOtxhrTxX5nyT7WpXSOLzHJO88oZH5wHNvyrRuEegMjwz0ucDbzcObpwTSvpyNomvKxj5HyESntHiR/M87vAZv/ZV3RIiIFXn4c4qxqAZl9vPC2Jmz9nHqHIMrczSCB4OJxFy9OrOTlPXcHcqL4j8PMvq/P6WzOHz9bC3NPyWJ4WW8cbkUkssXYhzmiWM+bG+YAA97wd9mkOviK2gYJlvcoULrsVeGRx2VzcEl2bIWdT4SPJ178tp8b5ZDBzxiNzTEwMLXea0cpDgSrbjNI3pONlXJ+LhS09p3TrsTSkDGRxyzzyxSSmGNp81jGQRN32A3cQN9itORTLAxnTXufb2ExeBxNnVYuYjTEc/k/XZjuyfXlfFJFHNYf2p7d8bJXhvKIwS4kgnYjr1R7nuhqHhVovRrbVQ+SpqPqS5HHNuVZ3QwOgPbV3OAe1zJH7jnBBIIduFraJlgQeidLw6N0xRxMMGOgEDTzNxNBtKtzEkkshaXBg6925PxkqcRFRw8PvynrP9cM/caiuKp3D78p6z/XDP3Goris+ldJ4R7QsiIiyoIiICIiAiIgIiICIiCO1HC+xp7KRRtLpH1ZWtaPSSwgKtaXe2TTWJc07tdUhIPxjkCuyqdrh83t5H4zN5LBwvcXmrTED4Q49SWtlify7nrs0gbknbqtuDiUxTNFU2616rOlFweQGQ9c839BS+7p5AZD1zzf0FL7uve+Hvx68C3a70XB5AZD1zzf0FL7unkBkPXPN/QUvu6Xw9+PXgW7Xei4PIDIeueb+gpfd08gMh655v6Cl93S+Hvx68C3a70XB5AZD1zzf0FL7unkBkPXPN/QUvu6Xw9+PXgW7Xei4PIDIeueb+gpfd08gMh655v6Cl93S+Hvx68C3a70XB5AZD1zzf0FL7unkBkPXPN/QUvu6Xw9+PXgW7Xei4PIDIeueb+gpfd08gMh655v6Cl93S+Hvx68C3a70XB5AZD1zzf0FL7unkBkPXPN/QUvu6Xw9+PXgW7Xei4PIDIeueb+gpfd1+maBubkS6uzUzD3t7Oozfr8bYAR+w+lS+Hvx68C3a88P2EXtWyg7sly4LTsfRUrMP/wCzXD9it65MViquEx8NKlCIK0QIa3cuJJJJcSdy5xJJLiSSSSSSSutYsauMSuao1cOYnnERF4oIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIP//Z",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7a29fe2",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc93cf40",
   "metadata": {},
   "source": [
    "### 2. 执行对话并在工具调用前中断\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12c06a58",
   "metadata": {},
   "source": [
    "我们现在来执行对话，并在工具节点之前进行中断，以便人类介入。\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "e9bc5112",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "我正在学习LangGraph。你能帮我做一些研究吗？\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_search_results_json (call_7aNeaPmidh0QCdwFlI5eSUZy)\n",
      " Call ID: call_7aNeaPmidh0QCdwFlI5eSUZy\n",
      "  Args:\n",
      "    query: LangGraph 研究\n"
     ]
    }
   ],
   "source": [
    "# 用户输入的消息\n",
    "user_input = \"我正在学习LangGraph。你能帮我做一些研究吗？\"\n",
    "# 配置新的对话线程 ID，用于保存和恢复对话状态\n",
    "config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
    "\n",
    "# 使用 stream 方法处理用户输入并返回事件\n",
    "events = graph.stream(\n",
    "    {\"messages\": [(\"user\", user_input)]},\n",
    "    config,\n",
    "    stream_mode=\"values\"\n",
    ")\n",
    "\n",
    "# 遍历每个事件并输出最后一条消息的内容\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        event[\"messages\"][-1].pretty_print()  # 打印消息内容"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10ea1621",
   "metadata": {},
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6a7db0f",
   "metadata": {},
   "source": [
    "检查图的状态，确认已经中断。\n",
    "\n",
    "**获取状态快照**：\n",
    "   - 使用 `graph.get_state(config)` 获取当前对话的状态快照。\n",
    "   - `snapshot.next` 显示下一个将要执行的节点（在中断时为 \"tools\" 节点）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "3894534a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('tools',)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 获取当前对话的状态快照\n",
    "snapshot = graph.get_state(config)\n",
    "# 查看快照中下一个要执行的节点\n",
    "snapshot.next"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "448e17fc-9ebb-407c-8d8b-40c4dbb7f654",
   "metadata": {},
   "source": [
    "请注意，与上次不同，“下一个”节点被设置为'tools'。我们在这里中断了！让我们检查一下工具调用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "a568e527-28b7-412f-8a48-d22e4ad55d90",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'tavily_search_results_json',\n",
       "  'args': {'query': 'LangGraph 研究'},\n",
       "  'id': 'call_7aNeaPmidh0QCdwFlI5eSUZy',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "existing_message = snapshot.values[\"messages\"][-1]\n",
    "existing_message.tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95436842-b3fa-4e0e-80f9-eed3aaa8f709",
   "metadata": {},
   "source": [
    "这个查询看起来很合理。这里没有需要过滤的内容。人类能做的最简单的事情就是让图形继续执行。我们在下面这样做。\n",
    "\n",
    "**接下来，继续执行图！**\n",
    "\n",
    "传入 `None` 便是让图从它停止的地方继续，而不会向状态中添加任何新内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "fd1c0708-cbf9-4040-8365-f2c162468689",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_search_results_json (call_7aNeaPmidh0QCdwFlI5eSUZy)\n",
      " Call ID: call_7aNeaPmidh0QCdwFlI5eSUZy\n",
      "  Args:\n",
      "    query: LangGraph 研究\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: tavily_search_results_json\n",
      "\n",
      "[{\"url\": \"https://zenn.dev/zenkigen_tech/articles/536801e61d0689\", \"content\": \"LangGraph\\u306eexamples\\u304b\\u3089\\u30a8\\u30fc\\u30b8\\u30a7\\u30f3\\u30c8\\u306e\\u4f5c\\u308a\\u65b9\\u3092\\u5b66\\u3076 ... \\u304b\\u3089\\u3001\\u30a2\\u30a4\\u30c7\\u30a2\\u5275\\u51fa\\u3001\\u5b9f\\u9a13\\u306e\\u5b9f\\u884c\\u3068\\u7d50\\u679c\\u306e\\u8981\\u7d04\\u3001\\u8ad6\\u6587\\u306e\\u57f7\\u7b46\\u3001\\u30d4\\u30a2\\u30ec\\u30d3\\u30e5\\u30fc\\u3068\\u3044\\u3063\\u305f\\u79d1\\u5b66\\u7814\\u7a76\\u306e\\u30b5\\u30a4\\u30af\\u30eb\\u3092\\u81ea\\u52d5\\u7684\\u306b\\u9042\\u884c\\u3059\\u308bAI\\u30b7\\u30b9\\u30c6\\u30e0\\u300cThe AI Scientist\\u300d\\u304c\\u767a\\u8868\\u3055\\u308c\\u8a71\\u984c\\u306b\\u306a\\u308a\\u307e\\u3057\\u305f\\u3002 ...\"}, {\"url\": \"https://python.langchain.ac.cn/docs/langgraph/\", \"content\": \"\\ud83e\\udd9c\\ud83d\\udd78\\ufe0fLangGraph. \\u26a1 Building language agents as graphs \\u26a1. Overview . LangGraph is a library for building stateful, multi-actor applications with LLMs, built on top of (and intended to be used with) LangChain.It extends the LangChain Expression Language with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner.\"}]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "关于LangGraph的研究，可以参考以下两个资源：\n",
      "\n",
      "1. [LangGraph的使用示例](https://zenn.dev/zenkigen_tech/articles/536801e61d0689)：这个链接提供了LangGraph的示例，包括如何进行实验以及应用的结果。它还讨论了如何在学术研究中应用LangGraph。\n",
      "\n",
      "2. [LangGraph的文档](https://python.langchain.ac.cn/docs/langgraph/)：这个文档介绍了LangGraph的概述和功能。LangGraph是一个用于构建有状态的多参与者应用程序的库，它建立在LangChain之上，旨在协调多个链（或参与者）在多个计算步骤中的操作。\n",
      "\n",
      "这些资源可以帮助你深入了解LangGraph的功能和应用场景。如果你有特定的问题或需要更详细的信息，请告诉我！\n"
     ]
    }
   ],
   "source": [
    "events = graph.stream(None, config, stream_mode=\"values\")\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6db4ea6f-15cf-44b8-a47f-3c69a7943ec2",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "347b87fc",
   "metadata": {},
   "source": [
    "### 3. 人工介入，修改工具执行结果"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7365a2b7",
   "metadata": {},
   "source": [
    "我们直接使用人工定义的内容，作为工具的返回结果。\n",
    "\n",
    "**手动插入工具调用结果**：\n",
    "   - 创建一个 `ToolMessage` 消息对象，内容是手动提供的工具调用结果，如 `\"LangGraph 是一个用于构建状态化、多参与者应用的库\"`。\n",
    "   - `tool_call_id` 使用之前的工具调用 ID 来关联这个工具调用消息。\n",
    "\n",
    "**更新状态**：\n",
    "   - 通过 `graph.update_state(config, {\"messages\": [tool_message]})` 手动更新对话状态，插入新的工具调用结果。\n",
    "\n",
    "**继续执行对话**：\n",
    "   - 使用 `graph.stream(None, config)` 继续执行对话，处理工具调用后的节点。\n",
    "   - 打印后续生成的消息，显示机器人对工具调用结果的响应。\n",
    "\n",
    "**工具调用最终输出是人工手动输入的内容**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "c95a79df",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "LangGraph 是一个用于构建状态化、多参与者应用的库。\n"
     ]
    }
   ],
   "source": [
    "# 手动生成一个工具调用的消息，并更新到对话状态中\n",
    "tool_message = ToolMessage(\n",
    "    content=\"LangGraph 是一个用于构建状态化、多参与者应用的库。\",  # 工具调用返回的内容\n",
    "    tool_call_id=snapshot.values[\"messages\"][-1].tool_calls[0][\"id\"]  # 关联工具调用的 ID\n",
    ")\n",
    "\n",
    "# 更新对话状态，加入工具调用的结果\n",
    "graph.update_state(config, {\"messages\": [tool_message]})\n",
    "\n",
    "# 继续执行对话，查看工具调用后的后续处理\n",
    "events = graph.stream(None, config, stream_mode=\"values\")\n",
    "\n",
    "for event in events:\n",
    "    event[\"messages\"][-1].pretty_print()  # 打印后续生成的消息内容"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bbdf42b",
   "metadata": {},
   "source": [
    "---------\n",
    "\n",
    "## 第5部分： 查看 ChatBot 历史对话\n",
    "\n",
    "在 LangGraph 中，可以使用 `graph.get_state_history()` 来获取对话的所有历史状态。\n",
    "\n",
    "传入参数：`{\"configurable\": {\"thread_id\": \"1\"}}`，可以获取指定线程的对话状态。\n",
    "\n",
    "```python\n",
    "# 获取指定线程 ID 的所有历史状态\n",
    "history = graph.get_state_history({\"configurable\": {\"thread_id\": \"1\"}})\n",
    "\n",
    "# 遍历历史记录，打印每个状态中的所有消息\n",
    "for state in history:\n",
    "    print(\"=== 对话历史 ===\")\n",
    "    # 遍历每个状态中的消息记录\n",
    "    for message in state.values[\"messages\"]:\n",
    "        if isinstance(message, BaseMessage):\n",
    "            # 根据消息类型区分用户与机器人\n",
    "            if \"user\" in message.content.lower():\n",
    "                print(f\"User: {message.content}\")\n",
    "            else:\n",
    "                print(f\"Assistant: {message.content}\")\n",
    "```\n",
    "\n",
    "\n",
    "### **为什么不用 events 获取历史？**\n",
    "\n",
    "如果您使用 events，每次调用 graph.stream() 时，您只能获取当前的对话步骤，而不是之前的对话记录。要保留对话历史，您需要在每次获取 events 时将结果手动保存，这样会增加复杂度。\n",
    "\n",
    "\n",
    "### 对话历史去重\n",
    "\n",
    "直接运行上面的代码，可能会出现重复的对话历史。\n",
    "\n",
    "- **问题来源**：重复的状态快照和无消息的中间状态导致了多次重复的对话历史和空对话历史。\n",
    "\n",
    "为了处理这些问题，我们可以引入更严格的过滤条件，并确保只打印那些包含**新消息**或有效消息的状态。\n",
    "\n",
    "- **解决方案**：通过检查消息类型和 ID 来过滤无效和重复的消息，并只输出真正包含对话内容的状态快照。\n",
    "\n",
    "### 代码解析\n",
    "\n",
    "1. **`valid_messages` 列表**：这段代码检查每条消息是否是 `BaseMessage` 类型，同时确保该消息之前没有被处理过。如果消息符合这些条件，则被视为有效消息。\n",
    "\n",
    "2. **输出条件**：只有当存在有效消息时，才打印 `=== 对话历史 ===`，否则打印 `=== 空对话历史（无有效消息） ===`。\n",
    "\n",
    "3. **避免重复输出**：通过 `seen_message_ids` 集合来存储已经处理过的消息 ID，确保每条消息只被打印一次，防止重复输出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "f8e2fe74-4dd8-4cd8-9b30-2a34efaf53da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 对话历史 ===\n",
      "Assistant: 我正在学习LangGraph。你能帮我做一些研究吗？\n",
      "Assistant: \n",
      "Assistant: [{\"url\": \"https://zenn.dev/zenkigen_tech/articles/536801e61d0689\", \"content\": \"LangGraph\\u306eexamples\\u304b\\u3089\\u30a8\\u30fc\\u30b8\\u30a7\\u30f3\\u30c8\\u306e\\u4f5c\\u308a\\u65b9\\u3092\\u5b66\\u3076 ... \\u304b\\u3089\\u3001\\u30a2\\u30a4\\u30c7\\u30a2\\u5275\\u51fa\\u3001\\u5b9f\\u9a13\\u306e\\u5b9f\\u884c\\u3068\\u7d50\\u679c\\u306e\\u8981\\u7d04\\u3001\\u8ad6\\u6587\\u306e\\u57f7\\u7b46\\u3001\\u30d4\\u30a2\\u30ec\\u30d3\\u30e5\\u30fc\\u3068\\u3044\\u3063\\u305f\\u79d1\\u5b66\\u7814\\u7a76\\u306e\\u30b5\\u30a4\\u30af\\u30eb\\u3092\\u81ea\\u52d5\\u7684\\u306b\\u9042\\u884c\\u3059\\u308bAI\\u30b7\\u30b9\\u30c6\\u30e0\\u300cThe AI Scientist\\u300d\\u304c\\u767a\\u8868\\u3055\\u308c\\u8a71\\u984c\\u306b\\u306a\\u308a\\u307e\\u3057\\u305f\\u3002 ...\"}, {\"url\": \"https://python.langchain.ac.cn/docs/langgraph/\", \"content\": \"\\ud83e\\udd9c\\ud83d\\udd78\\ufe0fLangGraph. \\u26a1 Building language agents as graphs \\u26a1. Overview . LangGraph is a library for building stateful, multi-actor applications with LLMs, built on top of (and intended to be used with) LangChain.It extends the LangChain Expression Language with the ability to coordinate multiple chains (or actors) across multiple steps of computation in a cyclic manner.\"}]\n",
      "Assistant: 关于LangGraph的研究，可以参考以下两个资源：\n",
      "\n",
      "1. [LangGraph的使用示例](https://zenn.dev/zenkigen_tech/articles/536801e61d0689)：这个链接提供了LangGraph的示例，包括如何进行实验以及应用的结果。它还讨论了如何在学术研究中应用LangGraph。\n",
      "\n",
      "2. [LangGraph的文档](https://python.langchain.ac.cn/docs/langgraph/)：这个文档介绍了LangGraph的概述和功能。LangGraph是一个用于构建有状态的多参与者应用程序的库，它建立在LangChain之上，旨在协调多个链（或参与者）在多个计算步骤中的操作。\n",
      "\n",
      "这些资源可以帮助你深入了解LangGraph的功能和应用场景。如果你有特定的问题或需要更详细的信息，请告诉我！\n",
      "Assistant: LangGraph 是一个用于构建状态化、多参与者应用的库。\n",
      "=== 空对话历史（无有效消息） ===\n",
      "=== 空对话历史（无有效消息） ===\n",
      "=== 空对话历史（无有效消息） ===\n",
      "=== 空对话历史（无有效消息） ===\n",
      "=== 空对话历史（无有效消息） ===\n"
     ]
    }
   ],
   "source": [
    "# 获取指定线程 ID 的所有历史状态\n",
    "history = graph.get_state_history({\"configurable\": {\"thread_id\": \"3\"}})\n",
    "\n",
    "# 使用集合存储已处理过的消息 ID\n",
    "seen_message_ids = set()\n",
    "\n",
    "# 遍历历史记录，打印每个状态中的所有消息\n",
    "for state in history:\n",
    "    # 获取状态中的消息列表\n",
    "    messages = state.values.get(\"messages\", [])\n",
    "    \n",
    "    # 检查是否存在至少一条未处理的 BaseMessage 类型的消息\n",
    "    valid_messages = [msg for msg in messages if isinstance(msg, BaseMessage) and msg.id not in seen_message_ids]\n",
    "    \n",
    "    if valid_messages:\n",
    "        print(\"=== 对话历史 ===\")\n",
    "        for message in valid_messages:\n",
    "            seen_message_ids.add(message.id)  # 记录消息 ID，避免重复处理\n",
    "            if \"user\" in message.content.lower():\n",
    "                print(f\"User: {message.content}\")\n",
    "            else:\n",
    "                print(f\"Assistant: {message.content}\")\n",
    "    else:\n",
    "        print(\"=== 空对话历史（无有效消息） ===\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43681f46-8d20-4002-92c3-f121cd52a8a8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5778ec5f-13e3-40f9-b17b-769e87e8980e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "82f58d4f-87a7-4d5d-b962-553b66407812",
   "metadata": {},
   "source": [
    "# Homework: \n",
    "\n",
    "1. 运行和测试第1部分的聊天机器人（ChatBot-Only），并尝试找到一个其无法回答正确的事实性问题。\n",
    "1. 使用联网查询工具（如：Tavily），在第2部分的聊天机器人（ChatBot + Tool）上测试相同问题，并对比生成结果。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0696cb2-16f9-4db0-a395-18e6246cea95",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
